package com.gitee.huanminabc.utils_tools.fast_cache.local;

import com.gitee.huanminabc.jcommon.system.AppStop;
import com.gitee.huanminabc.utils_tools.fast_cache.AbstractCache;
import com.gitee.huanminabc.utils_tools.fast_cache.Cache;
import com.gitee.huanminabc.utils_tools.fast_cache.CacheObj;
import com.gitee.huanminabc.utils_tools.fast_cache.MapDBUtil;
import com.gitee.huanminabc.utils_tools.fast_cache.memory.MemoryCache;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.mapdb.DB;
import org.mapdb.HTreeMap;

import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * 主要策略是将内存中的缓存在本地保存一份，当内存中的缓存被清除时，从本地文件中读取, 内存读取和mapdb读写速度相差2~5倍左右 ,而mapdb读写速度和文件相差至少100倍+  数据量越大差距越大
 * 经过测试可以达到每秒几十万数据的读写速度几乎和内存一样快
 * 1. 清理过期的缓存
 * 2. 容量快满了时，清除没有使用的缓存 也就是访问次数为0的缓存
 * 3. 容量满了时，清除最早访问的缓存,直到满足80%后停止
 * 4. 以上策略每隔5分钟执行一次
 * @param <V>
 */
@Slf4j
@Getter
public abstract class AbstractLocalCache<V> extends AbstractCache<V> implements Cache<String, V> {

    private String key;
    private MemoryCache<V> memoryCache;
    private DB mmp;
    private HTreeMap<String, CacheObj<String, V>> localCache;
    private LinkedBlockingQueue<Runnable> queueRun;


    protected AbstractLocalCache(String key, int capacity) {
        this(key, capacity, -1);
    }

    protected AbstractLocalCache(String key, int capacity, int timeout) {
        super(capacity, timeout);
        this.memoryCache = new MemoryCache<>(capacity, timeout);
        //将内存缓存的map赋值给memoryCache
        this.memoryCache.setCacheMap(this.cacheMap);
        this.key = key;
        this.queueRun = new LinkedBlockingQueue<>();
        mmp = MapDBUtil.defaultCreate(key);
        //每次启动都是新的HashMap, 删除创建时间超过7天的缓存项
        localCache = MapDBUtil.createHTreeMap(mmp, key);
        //启动前先清理一遍
        addQueueRun(this::prune);
        //添加关闭钩子
        AppStop.addShutdownHook(mmp::close);
        //持续监听队列然后执行,这样避免了频繁的IO操作导致的性能问题,虽然可能会导致延迟
        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    //单线程跑是要比多线程快
                    Runnable poll = queueRun.take();
                    poll.run();
                } catch (InterruptedException ignored) {
                }
            }
        });
        thread.setDaemon(true);
        thread.start();

        //将最近3天内访问过的缓存放入内存,这样就减少了和本地文件的IO操作
        addQueueRun(()->{
            Set<String> keys = localCache.getKeys();
            for (String key1 : keys) {
                CacheObj<String, V> stringVCacheObj = localCache.get(key1);
                if (stringVCacheObj == null) {
                    continue;
                }
                if (stringVCacheObj.getLastAccess() > System.currentTimeMillis() - 1000 * 60 * 60 * 24 * 3) {
                    memoryCache.putInside(stringVCacheObj);
                }
                stringVCacheObj=null;
            }
            keys.clear();
            keys=null;
        });
    }


    //20万数据1秒左右
    @Override
    public void pruneCache() {
        //先清理内存
        this.memoryCache.pruneCache();
        //再清理本地
        prune();
    }

    @Override
    protected void prune() {
        //拿到所有的key和value
        Iterator<Map.Entry<String, CacheObj<String, V>>> iterator = localCache.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, CacheObj<String, V>> entry = iterator.next();
            CacheObj<String, V> stringVCacheObj = entry.getValue();
            //1.gc回收   清理value为null的 ,一但value为null，说明已经被gc回收了
            if (stringVCacheObj == null) {
                iterator.remove();
                continue;
            }
            //跳过小于0的
            if (stringVCacheObj.getTtl() < 0) {
                continue;
            }
            //2.先清理缓存过期的
            if (stringVCacheObj.isExpired()) {
                iterator.remove();
                continue;
            }
            //3.如果内存快满了，清理没有使用的缓存
            if (isNearlyFull()) {
                //删除找到访问次数为0的
                if (stringVCacheObj.accessCount.get() == 0) {
                    iterator.remove();
                }
            }
        }

        //4.如果内存满了,按照访问时间来排序，删除最早访问的,直到满足80%
        if (isFull()) {
            Collection<CacheObj<String, V>> values = localCache.getValues();
            //从小到大排序 ,最早访问的在前面
            TreeSet<CacheObj<String, V>> treeSet = new TreeSet<>((o1, o2) -> {
                if (o1.getLastAccess() == o2.getLastAccess()) {
                    return 0;
                }
                return (int) (o1.getLastAccess() - o2.getLastAccess());
            });
            treeSet.addAll(values);
            values.clear();
            //删除最早访问的
            for (CacheObj<String, V> stringVCacheObj : treeSet) {
                cacheMap.remove(stringVCacheObj.getKey());
                if (isHalfFull()) {
                    break;
                }
            }
            treeSet.clear();
        }
        System.gc();//手动gc
        //持久化到本地, 这个是耗时很大的操作, 会将缓存数据全部刷到磁盘
        mmp.commit();
    }


    @Override
    public CacheObj<String, V> putInside(String key, V value) {
        //先放入内存
        CacheObj<String, V> cacheObj = this.memoryCache.putInside(key, value);
        addQueueRun(() -> {
            //判断是否关闭
            if (mmp.isClosed()) {
                return;
            }
            //再放入本地
            localCache.put(key, cacheObj);
        });
        return cacheObj;
    }

    @SneakyThrows
    public void addQueueRun(Runnable runnable) {
        queueRun.put(runnable);
    }

    @Override
    public CacheObj<String, V> putInside(String key, V value, long timeout) {
        //先放入内存
        CacheObj<String, V> cacheObj = this.memoryCache.putInside(key, value, timeout);
        addQueueRun(() -> {
            //再放入本地
            localCache.put(key, cacheObj);
        });
        return cacheObj;
    }

    @Override

    public V getInside(String key) {
        //先从内存中获取
        V value = this.memoryCache.getInside(key);
        if (value != null) {
            return value;
        }
        //再从本地获取
        CacheObj<String, V> cacheObj = localCache.get(key);
        if (cacheObj == null) {
            return null;
        }
        //判断是否过期
        if (cacheObj.isExpired()){
            localCache.remove(key);
            return null;
        }
        //添加到内存
        this.memoryCache.putInside(cacheObj);
        return cacheObj.getObj();
    }

    @Override
    public V removeInside(String key) {
        //先从内存中删除
        V value = this.memoryCache.removeInside(key);
        addQueueRun(() -> {
            //再从本地删除
            localCache.remove(key);
            //防止查询的时候又把缓存放入内存的情况
            this.memoryCache.removeInside(key);
        });
        return value;
    }

    @Override
    public void clearInside() {
        //先清理本地
        localCache.clear();
        mmp.commit();//提交修改
        //在清理内存
        this.memoryCache.clearInside();
    }


    //判断队列是否空
    public boolean isQueueEmpty() {
        return queueRun.isEmpty();
    }

    //等待队列为空
    public void waitQueueEmpty() {
        while (!isQueueEmpty()) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}
